package com.flirc
{
	import com.flirc.events.*;
	
	import flash.events.Event;
	import flash.events.EventDispatcher;

	[Event(name="onSockClose", type="com.flirc.events.IrcEvent")]
	[Event(name="onSockOpen", type="com.flirc.events.IrcEvent")]
	[Event(name="onSockError", type="com.flirc.events.IrcErrorEvent")]
	[Event(name="onStatusMessage", type="com.flirc.events.IrcMessageEvent")]
	[Event(name="onActiveMessage", type="com.flirc.events.IrcMessageEvent")]
	[Event(name="onPing", type="com.flirc.events.IrcEvent")]
	[Event(name="onIrcError", type="com.flirc.events.IrcErrorEvent")]
	[Event(name="onMotd", type="com.flirc.events.MotdEvent")]
	[Event(name="onNotice", type="com.flirc.events.NoticeEvent")]
	[Event(name="onJoin", type="com.flirc.events.JoinEvent")]
	[Event(name="onPart", type="com.flirc.events.PartEvent")]
	[Event(name="onText", type="com.flirc.events.PrivmsgEvent")]
	[Event(name="onTopic", type="com.flirc.events.TopicEvent")]
	[Event(name="onRawMode", type="com.flirc.events.ModeEvent")]
	[Event(name="onNick", type="com.flirc.events.NickEvent")]
	[Event(name="onKick", type="com.flirc.events.KickEvent")]
	[Event(name="onQuit", type="com.flirc.events.QuitEvent")]
	
	public class IRC extends EventDispatcher
	{
		
		private var sock:IRCSocket;
		private var _charset:String;
		private var _nickname:String;
		public var quitmsg:String;
		private var _ident:String = "this@is.just.something.for.us";
		private var registered:Boolean;
		
		public function IRC()
		{
			sock = new IRCSocket();
			
			sock.onSockClose = onSockClose;
			sock.onSockError = onSockError;
			sock.onSockOpen  = onSockOpen;
			sock.onSockRead  = onSockRead;
		}
		
		public function connect(server:String, port:uint, nick:String, charset:String = "windows-1251"):void
		{
			charset = charset;
			sock.connect(server, port);
			_nickname = nick;
			registered = false;
		}
		
		public function disconnect(): void
		{
			sock.sendString('QUIT : ' + quitmsg);
			sock.close();
		}
		
		private function onSockClose(e:Event):void
		{
			dispatchEvent(new Event(IrcEvent.ON_SOCK_CLOSE));
		}
		
		private function onSockError(err:String):void
		{
			dispatchEvent(new IrcErrorEvent(IrcErrorEvent.ON_SOCK_ERROR, err));
		}
		
		private function onSockOpen(e:Event):void
		{
			dispatchEvent(new IrcEvent(IrcEvent.ON_SOCK_OPEN));
			
			sock.sendString('NICK ' + _nickname);
			sock.sendString('USER ' + _ident + ' "jstClient" "127.0.0.1" :' + _ident);
		}
		
		private function onSockRead(input:String):void
		{
			if (!input) return;
			
			var tokens:Array = input.split(' ');
			
			var cmd:String = String(tokens[0]).toLowerCase();
			if (cmd == Commands.PING)
			{
				registered = true;	
				sock.sendString('PONG ' + tokens[1]);
				dispatchEvent(new IrcEvent(IrcEvent.ON_PING));
				return;
			}
			else if (cmd == Commands.ERROR)
			{
				sock.close();
				dispatchEvent(new IrcErrorEvent(IrcErrorEvent.ON_SOCK_ERROR, ((tokens.slice(1, tokens.length)).join(' ')).substring(1)));
				return;
			}				
			
			var m:Array = input.match(/\:(.+?)\:(.+)/);
			var message:String = (m != null ) ? m[2] : '';
			
			var event:String = String(tokens[1]).toLowerCase();
			
			var topic:TopicEvent;
			
			switch (event)
			{
				case Commands.RPL_WELCOME:
				case Commands.RPL_YOURHOST:
				case Commands.RPL_CREATED:
				case Commands.RPL_MYINFO:
				case Commands.RPL_BOUNCE:
				case Commands.RPL_LUSERCLIENT:
				case Commands.RPL_LUSEROP:
				case Commands.RPL_LUSERUNKNOWN:
				case Commands.RPL_LUSERCHANNELS:
				case Commands.RPL_LUSERME:{
					var tokens_:Array = input.split(':'); 
					dispatchEvent(new IrcMessageEvent(IrcMessageEvent.ON_STATUS_MESSAGE, tokens_[2]));
				}
					break;
				
				case Commands.ERR_NOSUCHNICK:
				case Commands.ERR_NOSUCHSERVER:
				case Commands.ERR_NOSUCHCHANNEL:
				case Commands.ERR_TOOMANYCHANNELS:
				case Commands.ERR_TOOMANYTARGETS:
				case Commands.ERR_NOSUCHSERVICE:
				case Commands.ERR_NOORIGIN:
				case Commands.ERR_NORECIPIENT:
				case Commands.ERR_NOTEXTTOSEND:
				case Commands.ERR_NOTOPLEVEL:
				case Commands.ERR_WILDTOPLEVEL:
				case Commands.ERR_BADMASK:
				case Commands.ERR_UNAVAILRESOURCE:
				case Commands.ERR_NOTONCHANNEL:
				case Commands.ERR_USERONCHANNEL:
				case Commands.ERR_NOLOGIN:
				case Commands.ERR_NOTREGISTERED:
				case Commands.ERR_NEEDMOREPARAMS:
				case Commands.ERR_ALREADYREGISTRED:
				case Commands.ERR_PASSWDMISMATCH:
				case Commands.ERR_YOUREBANNEDCREEP:
				case Commands.ERR_YOUWILLBEBANNED:
				case Commands.ERR_KEYSET:
				case Commands.ERR_NOPRIVILEGES:
				case Commands.ERR_CHANOPRIVSNEEDED:
				case Commands.ERR_UMODEUNKNOWNFLAG:
				case Commands.ERR_USERSDONTMATCH:
				case Commands.ERR_ERRONEUSNICKNAME:
				case Commands.ERR_NICKNAMEINUSE:
				case Commands.ERR_NOADMININFO:
				case Commands.ERR_NOMOTD:
				case Commands.ERR_NONICKNAMEGIVEN:
				case Commands.ERR_UNKNOWNCOMMAND:
				case Commands.ERR_WASNOSUCHNICK:
				case Commands.ERR_USERNOTINCHANNEL: {
					var err:IrcErrorEvent = new IrcErrorEvent(IrcErrorEvent.ON_IRC_ERROR, message);
					
					var attr:String = tokens[ 3];
					if (attr.substr(0, 1) != ':')
						err.message += ' "' + attr + '"';
					
					dispatchEvent(err);
				}
					break;
				
				case Commands.ERR_CANNOTSENDTOCHAN:
				case Commands.ERR_CHANNELISFULL:
				case Commands.ERR_UNKNOWNMODE:
				case Commands.ERR_INVITEONLYCHAN:
				case Commands.ERR_BANNEDFROMCHAN:
				case Commands.ERR_BADCHANNELKEY:
				case Commands.ERR_BADCHANMASK:
				case Commands.ERR_NOCHANMODES:
				case Commands.ERR_BANLISTFULL:{
					dispatchEvent(new IrcMessageEvent(IrcMessageEvent.ON_ACTIVE_MESSAGE, message));
				}
					break;
				
				case Commands.RPL_MOTDSTART:
				case Commands.RPL_MOTD:
				case Commands.RPL_ENDOFMOTD: {
					var motd: MotdEvent = new MotdEvent(MotdEvent.ON_MOTD);
					motd.msg = message;
					dispatchEvent(motd);
				}
					break;
				
				case Commands.RPL_NAMREPLY: {
					var list:Array = message.split(' ');
					var target:String = tokens[4];					
					var i:int = list.length;
					
					while (--i > -1)
					{
						if (list[i] == '' || list[i] == ' ' || list[i] == null)
							continue;
						
						var name:JoinEvent = new JoinEvent(JoinEvent.ON_JOIN);
						var user:User = new User();
						user.nick = list[i];
						
						name.user = user;
						name.chan = target;
						name.onJoin = false;
						
						dispatchEvent(name);
					}
					
					dispatchEvent(new IrcMessageEvent(IrcMessageEvent.ON_STATUS_MESSAGE, message));
				}
					break;
				
				case Commands.RPL_ENDOFNAMES: {
					dispatchEvent(new IrcMessageEvent(IrcMessageEvent.ON_STATUS_MESSAGE, message));					
				}
					break;
				
				
				case Commands.RPL_TOPIC: {
					topic = new TopicEvent(TopicEvent.ON_TOPIC);
					topic.mode = TopicEvent.MODE_TOPIC;
					topic.win = tokens[3];
					topic.msg = message;
					
					dispatchEvent(topic);
				}
					break;
				
				case Commands.RPL_TOPICSETBY: {
					topic = new TopicEvent(TopicEvent.ON_TOPIC);
					topic.mode = TopicEvent.MODE_SETBY;
					topic.win = tokens[3];
					topic.msg = tokens[4] + ' ' + tokens[5];
					
					dispatchEvent(topic);
				}
					break;
				
				case Commands.RPL_NOTOPIC: {
					topic = new TopicEvent(TopicEvent.ON_TOPIC);
					topic.mode = TopicEvent.MODE_NOTOPIC;
					topic.win = tokens[3];
					topic.msg = message;
					
					dispatchEvent(topic);
				}
					break;
				
				case Commands.NOTICE: {
					var notice:NoticeEvent = new NoticeEvent(NoticeEvent.ON_NOTICE);
					notice.text = message;
					notice.user = User.getUserFromAddress(tokens[0].substring(1));
					
					dispatchEvent(notice);
				}
					break;
				
				case Commands.RPL_CHANNELCREATED: {
					
				}
					break;
				
				case Commands.RPL_CHANNELMODES: {
					var mode:ModeEvent = new ModeEvent(ModeEvent.ON_RAW_MODE);
					mode.chan = tokens[3];
					mode.mode = tokens[4]
					
					for (var ii: int = 5; ii < tokens.length; ii++)
						mode.mode += ' ' + tokens[ii];
					
					dispatchEvent(mode);
				}
					break;
				
				case Commands.JOIN: {
					var join:JoinEvent = new JoinEvent(JoinEvent.ON_JOIN);
					join.user = User.getUserFromAddress(tokens[0].substring(1));
					
					join.chan = tokens[2];
					
					if (join.user.nick == _nickname)
						sock.sendString('MODE ' + join.chan);
					
					dispatchEvent(join);
				}
					break;
				
				case Commands.PART: {
					var part:PartEvent = new PartEvent(PartEvent.ON_PART);
					part.user = User.getUserFromAddress(tokens[0].substring(1));
					part.chan = tokens[2];
					part.msg = message;
					
					dispatchEvent(part);
				}
					break;
				
				case Commands.INVITE: {
					
				}
					break;
				
				case Commands.TOPIC: {
					var u:User = User.getUserFromAddress(tokens[0].substring(1));			
					var t:TopicEvent = new TopicEvent(TopicEvent.ON_TOPIC);
					var topicBy:TopicEvent = new TopicEvent(TopicEvent.ON_TOPIC);
					var time:Date = new Date();
					
					t.mode = TopicEvent.MODE_TOPIC;
					topicBy.mode = TopicEvent.MODE_SETBY;					
					
					t.win = topicBy.win = tokens[2];
					
					t.msg = message;
					topicBy.msg = u.nick + ' ' + ((time.getTime() / 1000) | 0);
					
					dispatchEvent(t);
					dispatchEvent(topicBy);
				}
					break;
				
				case Commands.PRIVMSG: {
					var pmsg: PrivmsgEvent = new PrivmsgEvent(PrivmsgEvent.ON_TEXT);
					pmsg.win = tokens[2];
					pmsg.user = User.getUserFromAddress(tokens[0].substring(1));
					pmsg.msg = message;
					
					dispatchEvent(pmsg);
				}
					break;
				
				case Commands.NICK: {
					var oldUser:User = User.getUserFromAddress(tokens[0].substring(1));
					
					if (oldUser.nick == _nickname)
						_nickname = message;
					
					var nick:NickEvent = new NickEvent(NickEvent.ON_NICK);
					
					nick.oldUser = oldUser;
					nick.newUser = new User();
					
					nick.newUser.nick = message;
					nick.newUser.host = oldUser.host;
					nick.newUser.ident = oldUser.ident;
					
					dispatchEvent(nick);
				}
					break;
				
				case Commands.MODE: {
					var mode:ModeEvent = new ModeEvent(ModeEvent.ON_RAW_MODE);
					mode.chan = tokens[2];
					mode.mode = tokens[3]
					mode.user = User.getUserFromAddress(tokens[0].substring(1));
					
					for (var i: int = 4; i < tokens.length; i++)
						mode.mode += ' ' + tokens[i];
					
					dispatchEvent(mode);
				}
					break;
				
				case Commands.KICK: {
					var kick:KickEvent = new KickEvent(KickEvent.ON_KICK);
					kick.agressor = User.getUserFromAddress(tokens[0].substring(1));
					kick.channel = tokens[2];
					kick.victim = new User();
					kick.victim.nick = tokens[3];
					kick.reason = (message == tokens[3]) ? '' : message;
					
					dispatchEvent(kick);
				}
					break;
				
				case Commands.QUIT: {
					var quit:QuitEvent = new QuitEvent(QuitEvent.ON_QUIT);
					quit.user = User.getUserFromAddress(tokens[0].substring(1));
					quit.msg = message;
					
					dispatchEvent(quit);
				}
					break;
				
				default: {
					dispatchEvent(new IrcMessageEvent(IrcMessageEvent.ON_STATUS_MESSAGE, input));
				}
			}
		}
		
		public function processInput(str:String):void
		{
			var tokens:Array = (str.substring(1)).split(' ');
			var cmd:String = tokens[0].toLowerCase();
			
			switch (cmd){
				case 'quit':
				case 'notice':
				case 'join':
					sock.sendString(str.substring(1));
					break;
				
				case 'list':
					break;
				
				case 'nick':
					sock.sendString('NICK :' + tokens[1]);
					break;
				
				default:
					sock.sendString(str.substring(1));
					break;
			}		
		}
		
		public function set charset(value:String):void
		{
			sock.charset = _charset = value;
		}
		
		public function get nickname():String
		{
			return _nickname;
		}
		
		public function get connected():Boolean
		{
			return sock.connected;
		}
		
		public function get ident():String
		{
			return _ident;
		}
		
		public function set ident(value:String):void
		{
			if (!connected) _ident = value;
		}
	}
}